package com.bpmn.flow.service.impl;

import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.bpmn.common.constant.BpmConstants;
import com.bpmn.common.constant.FlowableConstants;
import com.bpmn.common.enums.BpmTaskSignTypeEnum;
import com.bpmn.common.enums.BusinessStatusEnum;
import com.bpmn.common.enums.TaskOperationEnum;
import com.bpmn.common.exception.ServiceException;
import com.bpmn.flow.cmd.TaskRecallCmd;
import com.bpmn.flow.dto.TaskOperationDTO;
import com.bpmn.flow.service.TaskOperationService;
import com.bpmn.utils.FlowableUtils;
import com.bpmn.utils.ModelUtils;
import com.bpmn.utils.QueryUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import static com.bpmn.common.utils.CollectionUtils.convertListByFlatMap;

/**
 * @author rain
 * @date 2025年01月06日 10:20
 */
@Service
@Slf4j
public class TaskOperationServiceImpl implements TaskOperationService {

    @Resource
    private TaskService taskService;

    @Resource
    private RuntimeService runtimeService;

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private ManagementService managementService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void autoCompleteFirstTask(String processInstanceId) {
        // 获取流程启动后的第一个任务。
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).active().singleResult();
        ProcessInstance instance = QueryUtils.instanceQuery(processInstanceId).singleResult();
        log.info("[autoFirstTask][流程实例({}) 获取到第一个任务({})，发起人({})，任务代办人是（{}）]", processInstanceId, task.getId(), instance.getStartUserId(), task.getAssignee());
        if (StrUtil.equalsAny(task.getAssignee(), instance.getStartUserId()) && StrUtil.equals(task.getTaskDefinitionKey(), BpmConstants.APPLY_USER_TASK_KEY)) {
            log.info("[autoFirstTask][流程实例({}) 获取到第一个任务({})，并且是发起人与任务代办人相同({})，自动完成任务]", processInstanceId, task.getId(), task.getAssignee());
            // 这是第一个任务，设置处理人为流程发起人并自动完成
            TaskOperationDTO taskOperationDTO = new TaskOperationDTO();
            taskOperationDTO.setUserId(instance.getStartUserId());
            taskOperationDTO.setTaskId(task.getId());
            taskOperationDTO.setOperation(TaskOperationEnum.AGREE.getCode());
            taskOperationDTO.setComment("发起申请");
            this.handleTaskOperation(taskOperationDTO);
            log.info("自动完成申请人节点，first task[{}] task key[{}] for process[{}], startUser: {}",
                    task.getId(), task.getTaskDefinitionKey(), processInstanceId, instance.getStartUserId());
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void handleTaskOperation(TaskOperationDTO dto) {
        // 校验任务
        Task task = checkTask(dto.getTaskId(), dto.getUserId());
        // 获取操作类型
        TaskOperationEnum operation = TaskOperationEnum.getByCode(dto.getOperation());
        // 设置操作类型
        taskService.setVariable(task.getId(), FlowableConstants.OPERATION_TYPE, dto.getOperation());
        // 设置当前任务提交者
        taskService.setVariable(task.getId(), FlowableConstants.SUBMIT_USER_VAR, dto.getUserId());
        // 根据操作类型处理任务
        switch (operation) {
            case AGREE, REJECT, MULTI_AGREE, MULTI_REFUSE, MULTI_ABSTAIN -> completeTask(task, dto);
            case DELEGATE -> delegateTask(task, dto);
            case TRANSFER -> handleTransfer(task, dto);
            case TERMINATE -> handleTerminate(task, dto);
            case RETURN -> handleReturn(task, dto);
            case RETURN_TO_START -> handleReturnToStart(task, dto);
            case REJECT_BACK -> handleRejectBack(task, dto);
            case ADD_SIGN -> handleAddSign(task, dto);
            default -> throw new ServiceException("不支持的任务操作");
        }
    }

    /**
     * 校验任务
     */
    @Override
    public Task checkTask(String taskId, String userId) {
        Task task = QueryUtils.taskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new ServiceException("任务不存在！");
        }
        if (task.isSuspended()) {
            throw new ServiceException("当前任务已挂起不可审批！");
        }
        if (StrUtil.isNotBlank(task.getAssignee()) && ObjectUtil.notEqual(userId, task.getAssignee())) {
            throw new ServiceException("当前任务的审批人不是你！");
        }
        return task;
    }

    /**
     * 将指定流程实例的、进行中的流程任务，移动到结束节点
     *
     * @param processInstanceId 流程编号
     */
    @Override
    public void moveTaskToEnd(String processInstanceId) {
        List<Task> taskList = QueryUtils.taskQuery(processInstanceId).list();
        if (CollUtil.isEmpty(taskList)) {
            return;
        }
        // 终止流程
        BpmnModel bpmnModel = repositoryService.getBpmnModel(taskList.get(0).getProcessDefinitionId());
        List<String> activityIds = taskList.stream().map(Task::getTaskDefinitionKey).distinct().toList();
        EndEvent endEvent = ModelUtils.getEndEvent(bpmnModel);
        Assert.notNull(endEvent, "结束节点不能为空");
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(processInstanceId)
                .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId())
                .changeState();
    }

    /**
     * 办理任务
     */
    private void completeTask(Task task, TaskOperationDTO dto) {
        TaskEntityImpl taskEntity = (TaskEntityImpl) task;
        // 办理委派任务或者【后】加签的任务
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            // 创建子任务生成历史记录，委派或者【后】加签的人可在自己的已办任务中查看
            creatSubTaskAndComplete(task.getId(), task, dto, taskEntity);
            // 主任务办理意见
            taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
            // 完成任务
            taskService.resolveTask(dto.getTaskId(), dto.getVariables());
            return;
        }
        // 办理意见
        taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
        // 完成任务
        taskService.complete(dto.getTaskId(), dto.getVariables());
        //处理加签父任务，加签父任务为最开始加签的人
        String parentTaskId = taskEntity.getParentTaskId();
        if (StringUtils.isNotBlank(parentTaskId)) {
            String tableName = managementService.getTableName(TaskEntity.class);
            String sql = "select count(1) from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";
            long subTaskCount = taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
            if (subTaskCount == 0) {
                Task parentTask = taskService.createTaskQuery().taskId(parentTaskId).singleResult();
                String scopeType = parentTask.getScopeType();
                if (BpmTaskSignTypeEnum.of(scopeType) == null) {
                    return;
                }
                if (BpmTaskSignTypeEnum.BEFORE.getType().equals(scopeType)) {
                    // owner 重新赋值给父任务的 assignee，这样它就可以被审批
                    taskService.resolveTask(parentTaskId, dto.getVariables());
                } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) {
                    // 完成自己（因为它已经没有子任务，所以向后加签可以自动完成）
                    taskService.complete(parentTaskId, dto.getVariables());
                }
            }
        }
    }

    /**
     * 处理委派操作
     * 是将任务节点分给其他人处理，等其他人处理好之后，委派任务还会自动回到委派人的任务中
     */
    private void delegateTask(Task task, TaskOperationDTO dto) {
        String taskId = task.getId();
        if (StrUtil.isBlank(dto.getDelegateUserId())) {
            throw new ServiceException("委派目标用户不能为空");
        }
        // 1.添加审批意见
        taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
        // 2.设置审批人就是当前登录人
        taskService.setAssignee(dto.getTaskId(), dto.getUserId());
        // 3.执行委派，将任务委派给 delegateUser
        taskService.delegateTask(taskId, dto.getDelegateUserId());
        // 4.记录日志
        log.info("Task[{}] delegated to {}", task.getId(), dto.getDelegateUserId());
    }

    /**
     * 处理转办操作
     * 直接将办理人assignee 换成别人，这时任务的拥有着不再是转办人，而是为空，相当与将任务转出
     */
    private void handleTransfer(Task task, TaskOperationDTO dto) {
        if (StrUtil.isBlank(dto.getTransferUserId())) {
            throw new ServiceException("转办目标用户不能为空");
        }
        TaskEntityImpl taskEntity = (TaskEntityImpl) task;
        creatSubTaskAndComplete(task.getId(), task, dto, taskEntity);
        // 设置任务所有人 (owner) 为原任务的处理人 (assignee)
        if (StrUtil.isEmpty(task.getOwner())) {
            taskService.setOwner(task.getId(), task.getAssignee());
        }
        // 执行转派（审批人），将任务转派给 assigneeUser
        // 委托（ delegate）和转派（transfer）的差别，就在这块的调用！！！！
        taskService.setAssignee(task.getId(), dto.getTransferUserId());
        // 办理意见
        taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
        // 记录转办日志
        log.info("Task[{}] transferred from {} to {}", task.getId(), task.getAssignee(), dto.getTransferUserId());
    }

    /**
     * 创建子任务并完成
     */
    private void creatSubTaskAndComplete(String parentTaskId, Task task, TaskOperationDTO dto, TaskEntityImpl taskEntity) {
        // 创建子任务生成历史记录，办理的人可在自己的已办任务中查看
        Task subTask = FlowableUtils.createSubTask(taskEntity, parentTaskId, dto.getUserId());
        // 子任务办理意见
        taskService.addComment(subTask.getId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
        taskService.complete(subTask.getId());
    }

    /**
     * 处理终止操作
     */
    private void handleTerminate(Task task, TaskOperationDTO dto) {
        HistoricProcessInstance historicProcessInstance = QueryUtils.hisInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
        BusinessStatusEnum.checkInvalidStatus(historicProcessInstance.getBusinessStatus());
        try {
            String username = dto.getUserId();
            if (StrUtil.isBlank(dto.getComment())) {
                dto.setComment(username + "终止了申请");
            } else {
                dto.setComment(username + "终止了申请：" + dto.getComment());
            }
            // 办理意见
            taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
            List<Task> list = QueryUtils.taskQuery(task.getProcessInstanceId()).list();
            if (CollUtil.isNotEmpty(list)) {
                List<Task> subTasks = CollUtil.filter(list, e -> StringUtils.isNotBlank(e.getParentTaskId()));
                if (CollUtil.isNotEmpty(subTasks)) {
                    subTasks.forEach(e -> taskService.deleteTask(e.getId()));
                }
                runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.TERMINATION.getStatus());
                // 删除流程实例
                runtimeService.deleteProcessInstance(task.getProcessInstanceId(), "流程终止: " + dto.getComment());
            }
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }

    /**
     * 处理退回操作
     */
    private void handleReturn(Task task, TaskOperationDTO dto) {
        // 校验目标节点是否可以退回
        ModelUtils.validateTargetTaskCanReturn(task.getTaskDefinitionKey(), dto.getReturnTarget(), task.getProcessDefinitionId());
        String processInstanceId = task.getProcessInstanceId();
        ProcessInstance processInstance = QueryUtils.instanceQuery(task.getProcessInstanceId()).singleResult();
        // 校验流程状态是否可以退回
        BusinessStatusEnum.checkBackStatus(processInstance.getBusinessStatus());
        // 获得所有需要退回的任务(不管当前节点是并行多任务还是单任务，同意用List处理)
        List<Task> taskList = QueryUtils.taskQuery(processInstanceId).list();
        List<String> returnTaskKeyList = CollStreamUtil.toList(taskList, Task::getTaskDefinitionKey);
        // 获取当前用户
        String userId = dto.getUserId();
        List<String> runExecutionIds = new ArrayList<>();
        // 给当前要被退回的 task 数组，设置退回意见
        taskList.forEach(item -> {
            // 需要排除掉，不需要设置退回意见的任务
            if (!returnTaskKeyList.contains(item.getTaskDefinitionKey())) {
                return;
            }
            if (task.getExecutionId() != null) {
                runExecutionIds.add(task.getExecutionId());
            }
            // 判断是否分配给自己任务，因为会签任务，一个节点会有多个任务
            if (StrUtil.isNotBlank(task.getAssignee()) && ObjectUtil.equals(userId, task.getAssignee())) {
                // 情况一：自己的任务，进行 RETURN 标记
                // 添加评论
                taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
            }
        });
        //当前多个任务驳回到单个节点
        // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因：
        // 当多实例任务回退的时候有问题。相关 issue: https://github.com/flowable/flowable-engine/issues/3944
        runtimeService.createChangeActivityStateBuilder().processInstanceId(processInstanceId)
                .moveExecutionsToSingleActivityId(runExecutionIds, dto.getReturnTarget()).changeState();
    }

    /**
     * 处理退回到发起人操作
     */
    private void handleReturnToStart(Task task, TaskOperationDTO dto) {
        // 获取发起人节点
        UserTask startUserTask = ModelUtils.getApplyUserTask(task.getProcessDefinitionId());
        dto.setReturnTarget(startUserTask.getId());
        // 处理退回操作
        handleReturn(task, dto);
    }

    /**
     * 处理驳回操作（驳回跟退回区别是流程会直接结束）
     */
    private void handleRejectBack(Task task, TaskOperationDTO dto) {
        // 添加评论
        taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
        // 如果当前任务时被加签的，则加它的根任务也标记成未通过
        // 疑问：为什么要标记未通过呢？
        // 回答：例如说 A 任务被向前加签除 B 任务时，B 任务被审批不通过，此时 A 会被取消。
        if (task.getParentTaskId() != null) {
            String rootParentId = FlowableUtils.getTaskRootParentId(task);
            // 添加评论
            taskService.addComment(rootParentId, task.getProcessInstanceId(), dto.getOperation(), "加签任务不通过");
        }
        // 更新流程状态
        runtimeService.updateBusinessStatus(task.getProcessInstanceId(), BusinessStatusEnum.BACK.getStatus());
        // 结束流程
        moveTaskToEnd(task.getProcessInstanceId());
    }

    /**
     * 处理撤销操作
     */
    @Override
    public void handleRevoke(TaskOperationDTO dto) {
        // 验证是否是任务的办理人
        TaskRecallCmd taskRecallCmd = new TaskRecallCmd(dto.getTaskId(), dto.getUserId());
        managementService.executeCommand(taskRecallCmd);
    }

    /**
     * 处理加签操作
     * 加签就是让更多人人看到这个消息，比方说流程到你这里了，你拿不定主意，你让更多人帮你看看，给点意见
     * 1、向前加签
     * 任务在A这里，A这个时候需要BCD核对一下，等BCD核对之后又回到A这里
     * 2、向后加签
     * 任务在A这里，A这个时候需要BCD处理这个事情，处理完毕之后就不用管了，继续后面的审批环节
     */
    private void handleAddSign(Task task, TaskOperationDTO dto) {
        if (CollectionUtils.isEmpty(dto.getAddSignUsers())) {
            throw new ServiceException("加签人员不能为空");
        }
        // 向前加签和向后加签不能同时存在
        if (task.getScopeType() != null && ObjectUtil.notEqual(task.getScopeType(), dto.getAddSignType())) {
            throw new ServiceException(StrUtil.format("任务加签：当前任务已经{}，不能{}",
                    BpmTaskSignTypeEnum.nameOfType(task.getScopeType()), BpmTaskSignTypeEnum.nameOfType(dto.getAddSignType())));
        }
        //校验加签人员
        List<Task> taskList = QueryUtils.taskQuery(task.getProcessInstanceId()).taskDefinitionKey(task.getTaskDefinitionKey()).list();
        // 需要考虑 owner 的情况，因为向后加签时，它暂时没 assignee 而是 owner
        List<String> currentAssigneeList = convertListByFlatMap(taskList, item -> Stream.of(item.getAssignee(), item.getOwner()));
        if (CollUtil.containsAny(currentAssigneeList, dto.getAddSignUsers())) {
            throw new ServiceException("任务加签失败，加签人与现有审批人重复");
        }
        TaskEntityImpl taskEntity = (TaskEntityImpl) task;
        // 如果parentTaskId有值，代表是加签再加签，父任务id还是取parentTaskId
        // 如果parentTaskId为空，代表是加签，父任务id取当前任务id
        String parentTaskId = taskEntity.getParentTaskId();
        if (StringUtils.isBlank(parentTaskId)) {
            // 开启计数功能，主要用于为了让表 ACT_RU_TASK 中的 SUB_TASK_COUNT_ 字段记录下总共有多少子任务，后续可能有用
            taskEntity.setCountEnabled(true);
            // 向前加签，则将当前任务的 owner 赋值给当前任务的 assignee，并设置为空
            if (BpmTaskSignTypeEnum.BEFORE.getType().equals(dto.getAddSignType())) {
                taskEntity.setOwner(taskEntity.getAssignee());
                // 设置任务为空执行者
                taskEntity.setAssignee(null);
            }
            taskEntity.setScopeType(dto.getAddSignType());
            taskService.saveTask(taskEntity);
            parentTaskId = taskEntity.getId();
        }
        // 创建加签任务
        for (String userId : dto.getAddSignUsers()) {
            Task subTask = FlowableUtils.createNewSubTask(taskEntity, parentTaskId);
            subTask.setAssignee(userId);
            // 保存子任务
            taskService.saveTask(subTask);
        }
        //给自己创建一个子任务并完成用于历史任务的查询地方
        creatSubTaskAndComplete(parentTaskId, task, dto, taskEntity);
        // 办理意见
        taskService.addComment(dto.getTaskId(), task.getProcessInstanceId(), dto.getOperation(), dto.getComment());
    }

}
